Skip to main content

copp\copp\copp3\opt3/
copp3_socp.rs

1//! 3rd-order Convex-Objective Path Parameterization (COPP3) based on second-order cone programming (SOCP).
2//!
3//! # Method identity
4//! This module implements the **optimization backend** for COPP3 by transforming
5//! third-order path-parameterization constraints/objectives into Clarabel-compatible
6//! conic form and solving with SOCP.
7//!
8//! # Discrete variables (local notation)
9//! On a path grid `s[0..=n]`:
10//! - `a[k]` denotes $\dot{s}_k^2$;
11//! - `b[k]` denotes $\ddot{s}_k$;
12//! - decision vector starts with `x = [a[0..=n], b[0..=n], x_others]`, where
13//!   `x_others` are auxiliary variables introduced by objectives (`Time`,
14//!   `ThermalEnergy`, `TotalVariationTorque`, `Linear`).
15//!
16//! # High-level pipeline
17//! 1. Validate interval/boundary/objective contract.
18//! 2. Assemble standard TOPP3 conic constraints.
19//! 3. Add COPP3 objective-induced variables/cones.
20//! 4. Build sparse matrices `A`, `P`, vector `q`, and solve by Clarabel.
21//! 5. Apply status acceptance policy (`ClarabelOptions::is_allow`) and extract
22//!    `(a,b)` only when accepted.
23//!
24//! # API layering
25//! - `copp3_socp`: strict/normal API, returns only accepted `(a,b,num_stationary)`.
26//! - `copp3_socp_expert`: expert API returning `(Option<Copp3Result>, DefaultSolution<f64>)`.
27
28use crate::copp::clarabel_backend::{ConstraintsClarabel, ObjConsClarabel};
29use crate::copp::copp3::Copp3Result;
30use crate::copp::copp3::formulation::{Copp3Problem, get_weight_a_copp3, get_weight_a_topp3};
31use crate::copp::copp3::opt3::clarabel_constraints::{
32    clarabel_standard_capacity_topp3, clarabel_standard_constraint_topp3,
33};
34use crate::copp::{
35    ClarabelOptions, CoppObjective, clarabel_to_copp3_solution, validate_copp3_objectives,
36};
37use crate::diag::{
38    CoppError, DebugVerboser, SilentVerboser, SummaryVerboser, TraceVerboser, Verboser, Verbosity,
39    check_boundary_state_copp3_valid, check_s_interval_valid, format_duration_human,
40};
41use crate::robot::robot_core::{Robot, RobotBasic, RobotTorque};
42use clarabel::algebra::CscMatrix;
43use clarabel::solver::SupportedConeT::{NonnegativeConeT, SecondOrderConeT};
44use clarabel::solver::{DefaultSolution, DefaultSolver, IPSolver, SupportedConeT};
45use core::f64;
46use itertools::{Itertools, izip};
47use nalgebra::{DMatrix, DVectorView};
48
49/// Strict COPP3-SOCP API for production use.
50///
51/// # Purpose
52/// Use this entry when caller only needs a valid profile `(a,b,num_stationary)` and treats
53/// non-accepted solver statuses as hard failures.
54///
55/// # Contract
56/// - Internally calls [`copp3_socp_expert`].
57/// - Returns `Ok((a,b,num_stationary))` **iff** `options.is_allow(solution.status)` is `true`.
58/// - Returns [`Err(CoppError::ClarabelSolverStatus(...))`](CoppError::ClarabelSolverStatus) when status is not accepted.
59///
60/// # Returns
61/// Returns accepted COPP3 profile `(a, b, num_stationary)`.
62///
63/// # Errors
64/// Returns `CoppError` on model/solver failures and non-accepted solver status.
65///
66/// # Notes
67/// For workflows requiring low-level diagnostics (`status` and raw Clarabel solution fields),
68/// prefer [`copp3_socp_expert`].
69pub fn copp3_socp<'a, M: RobotTorque>(
70    problem: &Copp3Problem<'a, M>,
71    options: &ClarabelOptions,
72) -> Result<Copp3Result, CoppError> {
73    let (result, solution) = copp3_socp_expert(problem, options)?;
74    result.ok_or_else(|| CoppError::ClarabelSolverStatus("copp3_socp".into(), solution.status))
75}
76
77/// Expert COPP3-SOCP API with full Clarabel solution exposure.
78///
79/// # Return contract
80/// - `Ok((Some(result), solution))`: status accepted by `options.is_allow(solution.status)`.
81/// - `Ok((None, solution))`: solve finished but status not accepted.
82/// - `Err(...)`: input/model/solver-construction runtime failures.
83///
84/// # Returns
85/// Returns tuple `(Option<Copp3Result>, DefaultSolution<f64>)` for diagnostic use.
86///
87/// # Errors
88/// Returns [`CoppError`] only for true runtime failures.
89///
90/// # Contract
91/// - caller must handle `None` profile for non-accepted statuses;
92/// - status acceptance policy is defined by `options.is_allow`.
93///
94/// # Verbosity behavior
95/// Logging is layered by `options.verbosity()`:
96/// - [`Silent`](Verbosity::Silent): no algorithm logs;
97/// - [`Summary`](Verbosity::Summary): lifecycle milestones and elapsed time;
98/// - [`Debug`](Verbosity::Debug): assembly-level counters and stage summaries;
99/// - [`Trace`](Verbosity::Trace): fine-grained stage deltas and solver snapshot diagnostics.
100pub fn copp3_socp_expert<'a, M: RobotTorque>(
101    problem: &Copp3Problem<'a, M>,
102    options: &ClarabelOptions,
103) -> Result<(Option<Copp3Result>, DefaultSolution<f64>), CoppError> {
104    match options.verbosity() {
105        Verbosity::Silent => copp3_socp_core(problem, (options, SilentVerboser)),
106        Verbosity::Summary => copp3_socp_core(problem, (options, SummaryVerboser::new())),
107        Verbosity::Debug => copp3_socp_core(problem, (options, DebugVerboser::new())),
108        Verbosity::Trace => copp3_socp_core(problem, (options, TraceVerboser::new())),
109    }
110}
111
112/// Core implementation for COPP3-SOCP expert flow.
113///
114/// # Internal contract
115/// `options_verboser` packs:
116/// - `options`: acceptance policy and Clarabel numerical settings;
117/// - `verboser`: concrete logger implementation chosen by external verbosity dispatch.
118///
119/// # Invariants
120/// - decision-variable layout always starts with contiguous `a[0..=n]` and `b[0..=n]`;
121/// - `q_object.len()` is treated as final `n_var` before solver build;
122/// - extracted `(a,b)` is produced only through `clarabel_to_copp3_solution` when status is accepted.
123fn copp3_socp_core<'a, M: RobotTorque>(
124    problem: &Copp3Problem<'a, M>,
125    options_verboser: (&ClarabelOptions, impl Verboser),
126) -> Result<(Option<Copp3Result>, DefaultSolution<f64>), CoppError> {
127    let (options, mut verboser) = options_verboser;
128    let idx_s_start = problem.idx_s_start;
129    let a_boundary = problem.a_boundary;
130    let b_boundary = problem.b_boundary;
131    let num_stationary = problem.num_stationary;
132    if verboser.is_enabled(Verbosity::Summary) {
133        verboser.record_start_time();
134    }
135    if verboser.is_enabled(Verbosity::Trace) {
136        let settings = options.clarabel_settings();
137        crate::verbosity_log!(
138            crate::diag::Verbosity::Summary,
139            "copp3_socp: options snapshot -> allow(almost={}, max_iter={}, max_time={}, callback_term={}, insufficient_progress={}), tol_gap_rel={}, tol_feas={}, max_iter={}, verbose={}",
140            options.is_allow(clarabel::solver::SolverStatus::AlmostSolved),
141            options.is_allow(clarabel::solver::SolverStatus::MaxIterations),
142            options.is_allow(clarabel::solver::SolverStatus::MaxTime),
143            options.is_allow(clarabel::solver::SolverStatus::CallbackTerminated),
144            options.is_allow(clarabel::solver::SolverStatus::InsufficientProgress),
145            settings.tol_gap_rel,
146            settings.tol_feas,
147            settings.max_iter,
148            settings.verbose
149        );
150    }
151    // Check input validity
152    check_boundary_state_copp3_valid(a_boundary, b_boundary)?;
153    let n = problem.a_linearization.len() - 1;
154    let idx_s_final = idx_s_start + n;
155    if verboser.is_enabled(Verbosity::Summary) {
156        crate::verbosity_log!(
157            crate::diag::Verbosity::Summary,
158            "\ncopp3_socp started: {} <= idx_s <= {}, objectives = {}, s_len = {}.",
159            idx_s_start,
160            idx_s_final,
161            problem.objectives.len(),
162            problem.a_linearization.len()
163        );
164    }
165    check_s_interval_valid("copp3_socp", idx_s_start, idx_s_final)?;
166    validate_copp3_objectives(
167        "copp3_socp",
168        problem.objectives,
169        problem.robot.dim(),
170        problem.a_linearization.len(),
171    )?;
172    // Let x = [a[0,1,...,n],
173    //          b[0,1,...,n],
174    //          xi[0,1,...,len_xi-1], (if xi exists.)
175    //          x_others] \in R^{2*(n+1), x_others}.
176    // Step 1. Deal with constraints
177    // Step 1.1 Compute the number of constraints
178    let (cap_val_std, cap_b_std, cap_cone_std) =
179        clarabel_standard_capacity_topp3(&problem.robot.constraints, (idx_s_start, idx_s_final));
180    let (cap_val_obj, cap_b_obj, cap_cone_obj, n_vars) =
181        clarabel_objective_capacity_copp3(n, problem.objectives, problem.robot);
182    if verboser.is_enabled(Verbosity::Debug) {
183        crate::verbosity_log!(
184            crate::diag::Verbosity::Summary,
185            "copp3_socp: capacity estimate std(val={cap_val_std}, b={cap_b_std}, cone={cap_cone_std}), obj(val={cap_val_obj}, b={cap_b_obj}, cone={cap_cone_obj}), n_vars={n_vars}."
186        );
187    }
188    // s=b-A*x \in cone, where A[row[i],col[i]]=val[i], A \in R^{m*(n+1)}, b \in R^m, s \in R^m
189    // -s=-b+A*x
190    let mut row = Vec::<usize>::with_capacity(cap_val_std + cap_val_obj);
191    let mut col = Vec::<usize>::with_capacity(cap_val_std + cap_val_obj);
192    let mut val = Vec::<f64>::with_capacity(cap_val_std + cap_val_obj);
193    let mut b = Vec::<f64>::with_capacity(cap_b_std + cap_b_obj);
194    let mut cones = Vec::<SupportedConeT<f64>>::with_capacity(cap_cone_std + cap_cone_obj);
195    if verboser.is_enabled(Verbosity::Trace) {
196        crate::verbosity_log!(
197            crate::diag::Verbosity::Summary,
198            "copp3_socp: allocated capacities row/col/val/b/cones <= {}/{}/{}/{}/{}",
199            cap_val_std + cap_val_obj,
200            cap_val_std + cap_val_obj,
201            cap_val_std + cap_val_obj,
202            cap_b_std + cap_b_obj,
203            cap_cone_std + cap_cone_obj
204        );
205    }
206    // Step 1.2 set constraints of the standard topp3-lp problem
207    let s = problem
208        .robot
209        .constraints
210        .s_vec(idx_s_start, idx_s_final + 1)?;
211    let row_before_std = row.len();
212    let col_before_std = col.len();
213    let val_before_std = val.len();
214    let b_before_std = b.len();
215    let cones_before_std = cones.len();
216    clarabel_standard_constraint_topp3(
217        &problem.as_topp3_problem(),
218        &s,
219        (&mut row, &mut col, &mut val, &mut b, &mut cones),
220        num_stationary,
221        &verboser,
222    )?;
223    if verboser.is_enabled(Verbosity::Trace) {
224        crate::verbosity_log!(
225            crate::diag::Verbosity::Summary,
226            "copp3_socp: standard-constraints delta row/col/val/b/cones = +{}/+{}/+{}/+{}/+{}",
227            row.len() - row_before_std,
228            col.len() - col_before_std,
229            val.len() - val_before_std,
230            b.len() - b_before_std,
231            cones.len() - cones_before_std
232        );
233    }
234    // Step 2. set objective
235    // Step 2.1. determine whether xi=sqrt(a) is needed.
236    let row_before_sqrt = row.len();
237    let col_before_sqrt = col.len();
238    let val_before_sqrt = val.len();
239    let b_before_sqrt = b.len();
240    let cones_before_sqrt = cones.len();
241    let n_var_old = clarabel_sqrt_a_copp3(
242        n,
243        problem.objectives,
244        (&mut row, &mut col, &mut val, &mut b, &mut cones),
245        num_stationary,
246    );
247    if verboser.is_enabled(Verbosity::Trace) {
248        crate::verbosity_log!(
249            crate::diag::Verbosity::Summary,
250            "copp3_socp: sqrt-a stage delta row/col/val/b/cones = +{}/+{}/+{}/+{}/+{}, n_var_old={}",
251            row.len() - row_before_sqrt,
252            col.len() - col_before_sqrt,
253            val.len() - val_before_sqrt,
254            b.len() - b_before_sqrt,
255            cones.len() - cones_before_sqrt,
256            n_var_old
257        );
258    }
259    let mut q_object = Vec::<f64>::with_capacity(n_vars);
260    q_object.resize(n_var_old, 0.0);
261    // Step 2.2. add constraints and objective for each term in the objective.
262    let row_before_obj = row.len();
263    let col_before_obj = col.len();
264    let val_before_obj = val.len();
265    let b_before_obj = b.len();
266    let cones_before_obj = cones.len();
267    let q_before_obj = q_object.len();
268    clarabel_objective_copp3(
269        problem,
270        num_stationary,
271        (
272            &mut row,
273            &mut col,
274            &mut val,
275            &mut b,
276            &mut cones,
277            &mut q_object,
278        ),
279    )?;
280    if verboser.is_enabled(Verbosity::Trace) {
281        let (q_min, q_max) = q_object
282            .iter()
283            .fold((f64::INFINITY, f64::NEG_INFINITY), |(mn, mx), &v| {
284                (mn.min(v), mx.max(v))
285            });
286        crate::verbosity_log!(
287            crate::diag::Verbosity::Summary,
288            "copp3_socp: objective stage delta row/col/val/b/cones/q = +{}/+{}/+{}/+{}/+{}/+{}, q_range=[{}, {}]",
289            row.len() - row_before_obj,
290            col.len() - col_before_obj,
291            val.len() - val_before_obj,
292            b.len() - b_before_obj,
293            cones.len() - cones_before_obj,
294            q_object.len() - q_before_obj,
295            q_min,
296            q_max
297        );
298    }
299    if verboser.is_enabled(Verbosity::Debug) {
300        crate::verbosity_log!(
301            crate::diag::Verbosity::Summary,
302            "copp3_socp: after objective assembly row={}, col={}, val={}, b={}, cones={}, q={}",
303            row.len(),
304            col.len(),
305            val.len(),
306            b.len(),
307            cones.len(),
308            q_object.len()
309        );
310    }
311    // Step 2.3 build the constraints
312    let n_var = q_object.len();
313    let row_len = row.len();
314    let col_len = col.len();
315    let val_len = val.len();
316    let b_len = b.len();
317    let cones_len = cones.len();
318    let a_csc = CscMatrix::new_from_triplets(b.len(), n_var, row, col, val);
319    let p_object = CscMatrix::<f64>::zeros((n_var, n_var));
320    if verboser.is_enabled(Verbosity::Trace) {
321        crate::verbosity_log!(
322            crate::diag::Verbosity::Summary,
323            "copp3_socp: matrix built with m={}, n={}, A.nnz={}, P.nnz={}",
324            b.len(),
325            n_var,
326            a_csc.nnz(),
327            p_object.nnz()
328        );
329    }
330    if verboser.is_enabled(Verbosity::Summary) {
331        crate::verbosity_log!(
332            crate::diag::Verbosity::Summary,
333            "copp3_socp: ready to solve with row/col/val/b/cones = {row_len}/{col_len}/{val_len}/{b_len}/{cones_len} and n_var = {n_var}.",
334        );
335    }
336    // Step 3. solve the SOCP problem
337    let settings = options.clarabel_settings().clone();
338    let mut solver = DefaultSolver::<f64>::new(&p_object, &q_object, &a_csc, &b, &cones, settings)
339        .map_err(|e| CoppError::ClarabelSolverError("copp3_socp".into(), e))?;
340    solver.solve();
341    let solution = solver.solution;
342    if verboser.is_enabled(Verbosity::Summary) {
343        crate::verbosity_log!(
344            crate::diag::Verbosity::Summary,
345            "copp3_socp: solve done, status = {:?}, elapsed = {}.",
346            solution.status,
347            format_duration_human(verboser.elapsed())
348        );
349    }
350    if verboser.is_enabled(Verbosity::Trace) {
351        let show = solution.x.len().min(3);
352        crate::verbosity_log!(
353            crate::diag::Verbosity::Summary,
354            "copp3_socp: solution x_len={}, head={:?}",
355            solution.x.len(),
356            &solution.x[0..show]
357        );
358    }
359    let result = if options.is_allow(solution.status) {
360        let (a, b) =
361            clarabel_to_copp3_solution(&solution.x.as_slice()[0..2 * (n + 1)], &s, num_stationary);
362        Some((a, b, num_stationary))
363    } else {
364        None
365    };
366    if verboser.is_enabled(Verbosity::Trace) {
367        crate::verbosity_log!(
368            crate::diag::Verbosity::Summary,
369            "copp3_socp: allow(status)={}, extracted_profile={}",
370            options.is_allow(solution.status),
371            if result.is_some() {
372                "Some((a,b,num_stationary))"
373            } else {
374                "None"
375            }
376        );
377    }
378    Ok((result, solution))
379}
380
381/// Determine the length of xi[k] = sqrt(a[k + k_skip]) in the decision variable x.
382#[inline(always)]
383fn length_xi(n: usize, num_stationary: (usize, usize)) -> usize {
384    n + 1 - num_stationary.0.max(1) - num_stationary.1.max(1)
385}
386
387/// Return k_skip, where xi[k] = sqrt(a[k + k_skip])
388#[inline(always)]
389fn skip_a_for_xi(num_stationary_start: usize) -> usize {
390    num_stationary_start.max(1)
391}
392
393/// Add the constraints for sqrt(a) >= xi in COPP3 optimization.  
394/// x = [a[0,...,n], b[0,...,n], xi[0,...,len_xi-1], ...] \in R^{2*(n+1)+len_xi+...}.
395/// sqrt(a[k]) >= xi[k] >= 0  
396/// num_val <= 4*n, num_b <= 4*n, num_cones <= n  
397/// Return the len of the new x: n+1 or 2*(n+1)
398fn clarabel_sqrt_a_copp3(
399    n: usize,
400    objective: &[CoppObjective],
401    constraints: ConstraintsClarabel,
402    num_stationary: (usize, usize),
403) -> usize {
404    let (row, col, val, b, cones) = constraints;
405    for obj in objective {
406        match obj {
407            CoppObjective::Time(_) | CoppObjective::ThermalEnergy(_, _) => {
408                let n_skip = skip_a_for_xi(num_stationary.0);
409                let len_xi = length_xi(n, num_stationary); // < n
410                // xi >= 0
411                // A*x-b = -s = -1*xi[k] <= 0
412                row.extend(b.len()..b.len() + len_xi);
413                col.extend((2 * (n + 1))..(2 * (n + 1) + len_xi));
414                val.resize(val.len() + len_xi, -1.0);
415                b.resize(b.len() + len_xi, 0.0);
416                cones.push(NonnegativeConeT(len_xi));
417                // sqrt(a) >= xi
418                // xi^2 <= a
419                // xi^2 + (a - 0.25)^2 <= (a + 0.25)^2
420                // [a[k]+0.25, a[k]-0.25, xi[k]] \in SOC
421                // -A*x+b = s = [x[k+n_skip]+0.25, x[k+n_skip]-0.25, x[2*(n+1)+k]] \in SOC
422                row.extend(b.len()..b.len() + 3 * len_xi);
423                val.resize(val.len() + 3 * len_xi, -1.0);
424                cones.resize(cones.len() + len_xi, SecondOrderConeT(3));
425                for k in 0..len_xi {
426                    // row.extend(b.len()..b.len() + 3);
427                    col.extend([k + n_skip, k + n_skip, 2 * (n + 1) + k]);
428                    // val.resize(val.len() + 3, -1.0);
429                    b.extend([0.25, -0.25, 0.0]);
430                    // cones.push(SecondOrderConeT(3));
431                }
432                return 2 * (n + 1) + len_xi;
433            }
434            _ => {}
435        }
436    }
437    2 * (n + 1)
438}
439
440/// Determine the number of clarabel's capacity for the objective in COPP3.
441fn clarabel_objective_capacity_copp3<M: RobotBasic>(
442    n: usize,
443    objective: &[CoppObjective],
444    robot: &Robot<M>,
445) -> (usize, usize, usize, usize) {
446    // Step 1. sqrt(a[k]) >= xi[k] >= 0
447    // num_val <= 4*n, num_b <= 4*n, num_cones <= n, n_var <= n
448    let (mut capacity_val, mut capacity_b, mut capacity_cones, mut n_vars) =
449        if objective.iter().any(|obj| {
450            matches!(
451                obj,
452                CoppObjective::Time(_) | CoppObjective::ThermalEnergy(_, _)
453            )
454        }) {
455            (4 * n, 4 * n, n, 2 * (n + 1))
456        } else {
457            (0, 0, 0, 2 * (n + 1))
458        };
459    // Step 2. objective function
460    let dim = robot.dim();
461    for obj in objective {
462        match obj {
463            CoppObjective::Time(_) => {
464                // num_val <= 5*n, num_b <= 4*n, num_cones <= n, n_var <= n
465                capacity_val += 5 * n;
466                capacity_b += 4 * n;
467                capacity_cones += n;
468                n_vars += n;
469            }
470            CoppObjective::ThermalEnergy(_, _) => {
471                // num_val <= (4+2*dim)*(n+1), num_b <= (2+dim)*(n+1), num_cones <= n+1, n_var <= n+1
472                capacity_val += (4 + 2 * dim) * (n + 1);
473                capacity_b += (dim + 2) * (n + 1);
474                capacity_cones += n + 1;
475                n_vars += n + 1;
476            }
477            CoppObjective::TotalVariationTorque(_, _) => {
478                // num_val <= 10*n*dim, num_b <= 2*n*dim, num_cones <= 1, n_var <= n*dim
479                capacity_val += 10 * dim * n;
480                capacity_b += 2 * dim * n;
481                capacity_cones += 1;
482                n_vars += dim * n;
483            }
484            _ => {}
485        }
486    }
487    (capacity_val, capacity_b, capacity_cones, n_vars)
488}
489
490fn clarabel_objective_copp3<M: RobotTorque>(
491    problem: &Copp3Problem<M>,
492    num_stationary: (usize, usize),
493    objective_constraints: ObjConsClarabel,
494) -> Result<(), CoppError> {
495    let (row, col, val, b, cones, q_object) = objective_constraints;
496    let n = problem.a_linearization.len() - 1;
497    let s = problem
498        .robot
499        .constraints
500        .s_vec(problem.idx_s_start, problem.idx_s_start + n + 1)?;
501    let weight_a_time = if problem
502        .objectives
503        .iter()
504        .any(|obj| matches!(obj, CoppObjective::Time(_)))
505    {
506        get_weight_a_topp3(&s, num_stationary)
507    } else {
508        vec![]
509    };
510    let weight_a_torque = if problem
511        .objectives
512        .iter()
513        .any(|obj| matches!(obj, CoppObjective::ThermalEnergy(_, _)))
514    {
515        get_weight_a_copp3(&s, num_stationary)
516    } else {
517        vec![]
518    };
519    let coeffs_torque = if problem.objectives.iter().any(|obj| {
520        matches!(
521            obj,
522            CoppObjective::ThermalEnergy(_, _) | CoppObjective::TotalVariationTorque(_, _)
523        )
524    }) {
525        // shape: (dim, n) since there are n+1 a and n b.
526        problem.robot.torque_coeff(problem.idx_s_start, n + 1)
527    } else {
528        (
529            DMatrix::<f64>::zeros(0, 0),
530            DMatrix::<f64>::zeros(0, 0),
531            DMatrix::<f64>::zeros(0, 0),
532        )
533    };
534    for obj in problem.objectives {
535        match obj {
536            CoppObjective::Time(weight) => {
537                if !clarabel_objective_time_copp3(
538                    &s,
539                    *weight,
540                    &weight_a_time,
541                    num_stationary,
542                    (row, col, val, b, cones, q_object),
543                ) {
544                    return Err(CoppError::InvalidInput(
545                        "clarabel_objective_copp3".into(),
546                        "Invalid Time objective".into(),
547                    ));
548                }
549            }
550            CoppObjective::ThermalEnergy(weight, normalize) => {
551                if !clarabel_objective_thermal_energy_copp3(
552                    &weight_a_torque,
553                    *weight,
554                    normalize,
555                    &coeffs_torque,
556                    num_stationary,
557                    (row, col, val, b, cones, q_object),
558                ) {
559                    return Err(CoppError::InvalidInput(
560                        "clarabel_objective_copp3".into(),
561                        "Invalid ThermalEnergy objective".into(),
562                    ));
563                }
564            }
565            CoppObjective::TotalVariationTorque(weight, normalize) => {
566                if !clarabel_objective_tv_torque_copp3(
567                    *weight,
568                    normalize,
569                    &coeffs_torque,
570                    num_stationary,
571                    (row, col, val, b, cones, q_object),
572                ) {
573                    return Err(CoppError::InvalidInput(
574                        "clarabel_objective_copp3".into(),
575                        "Invalid TotalVariationTorque objective".into(),
576                    ));
577                }
578            }
579            CoppObjective::Linear(weight, alpha, beta) => {
580                if !clarabel_objective_linear_copp3(
581                    &s,
582                    *weight,
583                    alpha,
584                    beta,
585                    q_object,
586                    num_stationary,
587                ) {
588                    return Err(CoppError::InvalidInput(
589                        "clarabel_objective_copp3".into(),
590                        "Invalid Linear objective".into(),
591                    ));
592                }
593            }
594        }
595    }
596    Ok(())
597}
598
599/// Add the constraints and objective for Time in COPP3 optimization.  
600/// num_val <= 5*n, num_b <= 4*n, num_cones <= n, n_var <= n
601fn clarabel_objective_time_copp3(
602    s: &[f64],
603    weight: f64,
604    weight_a: &[f64],
605    num_stationary: (usize, usize),
606    objective_constraints: ObjConsClarabel,
607) -> bool {
608    if weight < 0.0 {
609        return false;
610    }
611
612    let (row, col, val, b, cones, q_object) = objective_constraints;
613    let n = s.len() - 1;
614    let len_xi = length_xi(n, num_stationary);
615    let k_skip = skip_a_for_xi(num_stationary.0);
616    // Add constraints for xi and eta, where xi[k]=sqrt(a[k+k_skip]), eta[k]=1/xi[k]
617    // eta[k] >= 0,
618    // norm2([2, xi[k] - eta[k]]) <= xi[k] + eta[k]
619    let id_xi_start = 2 * (n + 1);
620    let id_eta_start = q_object.len();
621    // Step 1. eta[k] >= 0
622    // A*x-b = -s = -1*eta[k] = -1*x[id_eta_start + k] <= 0
623    row.extend(b.len()..(b.len() + len_xi));
624    col.extend(id_eta_start..(id_eta_start + len_xi));
625    val.resize(val.len() + len_xi, -1.0);
626    b.resize(b.len() + len_xi, 0.0);
627    cones.push(NonnegativeConeT(len_xi));
628    // Step 2. norm2([2, xi[k] - eta[k]]) <= xi[k] + eta[k]
629    // -A*x+b = s = [xi[k] + eta[k], xi[k] - eta[k], 2] \in SOC
630    for k in 0..len_xi {
631        // -A*x+b = s = [x[id_xi_start + k] + x[id_eta_start + k], x[id_xi_start + k] - x[id_eta_start + k], 2] \in SOC
632        col.extend([
633            id_xi_start + k,
634            id_eta_start + k,
635            id_xi_start + k,
636            id_eta_start + k,
637        ]);
638        row.extend([b.len(), b.len(), b.len() + 1, b.len() + 1]);
639        val.extend([-1.0, -1.0, -1.0, 1.0]);
640        b.extend([0.0, 0.0, 2.0]);
641    }
642    cones.resize(cones.len() + len_xi, SecondOrderConeT(3));
643    // Minimize sum[k in 0..len_xi] {weight_a[k+k_skip] / sqrt(a[k+k_skip])}
644    // Minimize sum[k in 0..len_xi] {weight_a[k+k_skip] * eta[k]}
645    q_object.extend(
646        weight_a
647            .iter()
648            .skip(k_skip)
649            .take(len_xi)
650            .map(|w| weight * w),
651    );
652
653    true
654}
655
656/// Add the constraints and objective for ThermalEnergy in COPP3 optimization.  
657/// num_val <= (4+2*dim)*(n+1), num_b <= (2+dim)*(n+1), num_cones <= n+1, n_var <= n+1
658fn clarabel_objective_thermal_energy_copp3(
659    weight_a: &[f64],
660    weight: f64,
661    normalize: &[f64],
662    coeffs_torque: &(DMatrix<f64>, DMatrix<f64>, DMatrix<f64>),
663    num_stationary: (usize, usize),
664    objective_constraints: ObjConsClarabel,
665) -> bool {
666    if weight < 0.0 {
667        return false;
668    }
669    let (row, col, val, b, cones, q_object) = objective_constraints;
670    // minimize: weight * \int_{s[0]}^{s[n]} {\sum[i] {(tau[i][s] * normalize[i])^2 / sqrt(a[s])} ds}
671    let mut coeff_a = coeffs_torque.0.clone();
672    let mut coeff_b = coeffs_torque.1.clone();
673    let mut coeff_g = coeffs_torque.2.clone();
674    let dim = coeff_a.nrows();
675    if normalize.len() != dim {
676        return false;
677    }
678    let n = coeff_a.ncols() - 1;
679    // tau[i][k] = coeff_a[i][k] * a[k] + coeff_b[i][k] * b[k] + coeff_g[i][k]
680    let normalize = DVectorView::from_slice(normalize, dim);
681    for mut col in coeff_a.column_iter_mut() {
682        col.component_mul_assign(&normalize);
683    }
684    for mut col in coeff_b.column_iter_mut() {
685        col.component_mul_assign(&normalize);
686    }
687    for mut col in coeff_g.column_iter_mut() {
688        col.component_mul_assign(&normalize);
689    }
690    // tau[i][k] * normalize[i] = coeff_a[i][k] * a[k] + coeff_b[i][k] * b[k] + coeff_g[i][k]
691    let k_skip = skip_a_for_xi(num_stationary.0);
692    let len_xi = length_xi(n, num_stationary);
693
694    if num_stationary.0 > 0 {
695        // minimize: weight * \int_{s[0]}^{s[num_stationary.0]} {\sum[i] {tau_normal[i][s]^2 / sqrt(a[s])} ds}
696        // \approx weight * \sum[i] { tau_average[i]^2 * \int_{s[0]}^{s[num_stationary.0]} {1 / sqrt(a[s])} ds} }
697        // \int_{s[0]}^{s[num_stationary.0]} {1 / sqrt(a[s])} ds} = weight[0] / xi[0]
698        // minmize: weight * weight_a[0] * \sum[i] { tau_average[i]^2 / xi[0] }
699        // let tau_average[i] = (tau_normal[i][0] + tau_normal[i][num_stationary.0]) / 2
700        let col_a = coeff_a.column(num_stationary.0);
701        let col_b = coeff_b.column(num_stationary.0);
702        let col_g = coeff_g.column(num_stationary.0);
703        let col_g_0 = coeff_g.column(0);
704        // tau[0] = col_g_0
705        // tau[num_stationary.0] = col_a * a[num_stationary.0] + col_b * b[num_stationary.0] + col_g
706        // let \sum[i] { tau_average[i]^2 } <= t * xi[0]
707        // \sum[i](col_a[i] * a[num_stationary.0] + col_b[i] * b[num_stationary.0] + col_g[i] + col_g_0[i])^2 <= 4 * t
708        // -A*x+b = s = [t + xi[0], t - xi[0], -(col_a[i] * a[num_stationary.0] + col_b[i] * b[num_stationary.0] + col_g[i] + col_g_0[i])] \in SOC
709        let id_t = q_object.len();
710        // -A*x+b = [t + xi[0], t - xi[0]]
711        row.extend(b.len()..(b.len() + 2));
712        col.resize(col.len() + 2, id_t);
713        row.extend(b.len()..(b.len() + 2));
714        col.resize(col.len() + 2, 2 * (n + 1));
715        val.resize(val.len() + 3, -1.0);
716        val.push(1.0);
717        b.resize(b.len() + 2, 0.0);
718        // -A*x+b = [-(col_a[i] * a[num_stationary.0] + col_b[i] * b[num_stationary.0] + col_g[i] + col_g_0[i])] for i in 0..dim
719        row.extend((b.len())..(b.len() + dim));
720        col.resize(col.len() + dim, num_stationary.0);
721        val.extend(col_a.iter().take(dim));
722        row.extend((b.len())..(b.len() + dim));
723        col.resize(col.len() + dim, n + 1 + num_stationary.0);
724        val.extend(col_b.iter().take(dim));
725        b.extend(
726            col_g
727                .iter()
728                .zip(col_g_0.iter())
729                .take(dim)
730                .map(|(&g, &g_0)| -(g + g_0)),
731        );
732        cones.push(SecondOrderConeT(dim + 2));
733        q_object.push(weight * weight_a[0]);
734    }
735    if num_stationary.1 > 0 {
736        // minimize: weight * \int_{s[n-num_stationary.1]}^{s[n]} {\sum[i] {tau_normal[i][s]^2 / sqrt(a[s])} ds}
737        // \approx weight * \sum[i] { tau_average[i]^2 * \int_{s[n-num_stationary.1]}^{s[n]} {1 / sqrt(a[s])} ds} }
738        // \int_{s[n-num_stationary.1]}^{s[n]} {1 / sqrt(a[s])} ds} = weight[n-num_stationary.1] / xi[len_xi-1]
739        // minmize: weight * weight[n] * \sum[i] { tau_average[i]^2 }
740        // let tau_average[i] = (tau_normal[i][n] + tau_normal[i][n-num_stationary.1]) / 2
741        let col_a = coeff_a.column(n - num_stationary.1);
742        let col_b = coeff_b.column(n - num_stationary.1);
743        let col_g = coeff_g.column(n - num_stationary.1);
744        let col_g_f = coeff_g.column(n);
745        // tau[f] = col_g_n
746        // tau[n-num_stationary.1] = col_a * a[n-num_stationary.1] + col_b * b[n-num_stationary.1] + col_g
747        // let \sum[i] { tau_average[i]^2 } <= t * xi[len_xi-1]
748        // \sum[i](col_a[i] * a[n-num_stationary.1] + col_b[i] * b[n-num_stationary.1] + col_g[i] + col_g_f[i])^2 <= 4 * t * xi[len_xi-1]
749        // -A*x+b = s = [t + xi[len_xi-1], t - xi[len_xi-1], -(col_a[i] * a[n-num_stationary.1] + col_b[i] * b[n-num_stationary.1] + col_g[i] + col_g_f[i])] \in SOC
750        let id_t = q_object.len();
751        // -A*x+b = [t+xi[len_xi-1], t-xi[len_xi-1]]
752        row.extend(b.len()..(b.len() + 2));
753        col.resize(col.len() + 2, id_t);
754        row.extend(b.len()..(b.len() + 2));
755        col.resize(col.len() + 2, 2 * n + len_xi + 1);
756        val.resize(val.len() + 3, -1.0);
757        val.push(1.0);
758        b.resize(b.len() + 2, 0.0);
759        // -A*x+b = [-(col_a[i] * a[n-num_stationary.1] + col_b[i] * b[n-num_stationary.1] + col_g[i] + col_g_f[i])] for i in 0..dim
760        row.extend((b.len())..(b.len() + dim));
761        col.resize(col.len() + dim, n - num_stationary.1);
762        val.extend(col_a.iter().take(dim));
763        row.extend((b.len())..(b.len() + dim));
764        col.resize(col.len() + dim, 2 * n + 1 - num_stationary.1);
765        val.extend(col_b.iter().take(dim));
766        b.extend(
767            col_g
768                .iter()
769                .zip(col_g_f.iter())
770                .take(dim)
771                .map(|(&g, &g_f)| -(g + g_f)),
772        );
773        cones.push(SecondOrderConeT(dim + 2));
774        q_object.push(weight * weight_a[n]);
775    }
776
777    // minimize: weight * \sum[k] { \int_{s[k]}^{s[k+1]} {\sum[i] {tau_normal[i][s]^2 / sqrt(a[s])} ds} }
778    // Decouple the integral
779    // minimize: weight * \sum[k] { \sum[i] {tau_normal[i][k]^2} / sqrt(a[k]) * 0.5 * (s[k+1]-s[k-1]) }
780    let id_t_start = q_object.len();
781    for (k, (col_a, col_b, col_g)) in izip!(
782        coeff_a.column_iter(),
783        coeff_b.column_iter(),
784        coeff_g.column_iter()
785    )
786    .skip(k_skip)
787    .take(len_xi)
788    .enumerate()
789    {
790        // \sum[i] {(col_a[i] * a[k+k_skip] + col_b[i] * b[k+k_skip] + col_g[i])^2} / xi[k] <= 4 * t[k]
791        // \sum[i] {(col_a[i] * a[k+k_skip] + col_b[i] * b[k+k_skip] + col_g[i])^2} <= 4 * t[k] * xi[k]
792        // -A*x+b = s = [t[k] + xi[k], t[k] - xi[k], -(col_a[i] * a[k+k_skip] + col_b[i] * b[k+k_skip] + col_g[i])] \in SOC
793        // -A*x+b = s = [t[k] + xi[k], t[k] - xi[k]]
794        row.extend(b.len()..(b.len() + 2));
795        col.resize(col.len() + 2, id_t_start + k);
796        row.extend(b.len()..(b.len() + 2));
797        col.resize(col.len() + 2, 2 * (n + 1) + k);
798        val.resize(val.len() + 3, -1.0);
799        val.push(1.0);
800        b.resize(b.len() + 2, 0.0);
801        // -A*x+b = s = [-(col_a[i] * a[k+k_skip] + col_b[i] * b[k+k_skip] + col_g[i])]
802        row.extend((b.len())..(b.len() + dim));
803        col.resize(col.len() + dim, k + k_skip);
804        val.extend(col_a.iter().take(dim));
805        row.extend((b.len())..(b.len() + dim));
806        col.resize(col.len() + dim, n + 1 + k + k_skip);
807        val.extend(col_b.iter().take(dim));
808        b.extend(col_g.iter().take(dim).map(|&g| -g));
809    }
810    cones.resize(cones.len() + len_xi, SecondOrderConeT(dim + 2));
811    // minimize: 2 * weight * \sum[k] { t[k] * (s[k+1]-s[k-1]) }
812    // if num_stationary == (0,0), then \sum[k in 1..n] { t[k] * (s[k+1]-s[k-1]) }
813    // if num_stationary == (n1>0,n2>0), then \sum[k in (n1+1)..(n-n2-1)] { t[k] * (s[k+1]-s[k-1]) } + t[n1] * (s[n1+1]-s[n1]) + t[n-n2] * (s[n-n2]-s[n-n2-1])
814    let weight_four = 4.0 * weight;
815    q_object.extend(
816        weight_a
817            .iter()
818            .skip(k_skip)
819            .take(len_xi)
820            .map(|w| weight_four * w),
821    );
822    true
823}
824
825/// Add the constraints and objective for TotalVariationTorque in COPP3 optimization.  
826/// num_val <= 10*n*dim, num_b <= 2*n*dim, num_cones <= 1, n_var <= n*dim
827fn clarabel_objective_tv_torque_copp3(
828    weight: f64,
829    normalize: &[f64],
830    coeffs_torque: &(DMatrix<f64>, DMatrix<f64>, DMatrix<f64>),
831    num_stationary: (usize, usize),
832    objective_constraints: ObjConsClarabel,
833) -> bool {
834    if weight < 0.0 {
835        return false;
836    }
837    let (row, col, val, b, cones, q_object) = objective_constraints;
838    // minimize: weight * \sum |tau[i][k+1]-tau[i][k]| * normalize[i]
839    // Let: |tau[i][k+1]-tau[i][k]| * normalize[i] <= t[i][k]
840    let mut coeff_a = coeffs_torque.0.clone();
841    let mut coeff_b = coeffs_torque.1.clone();
842    let mut coeff_g = coeffs_torque.2.clone();
843    let dim = coeff_a.nrows();
844    if normalize.len() != dim {
845        return false;
846    }
847    let n = coeff_a.ncols() - 1;
848    // tau[i][k] = coeff_a[i][k] * a[k] + coeff_b[i][k] * b[k] + coeff_g[i][k]
849    let normalize = DVectorView::from_slice(normalize, dim);
850    for mut col in coeff_a.column_iter_mut() {
851        col.component_mul_assign(&normalize);
852    }
853    for mut col in coeff_b.column_iter_mut() {
854        col.component_mul_assign(&normalize);
855    }
856    for mut col in coeff_g.column_iter_mut() {
857        col.component_mul_assign(&normalize);
858    }
859    // tau[i][k] * normalize[i] = coeff_a[i][k] * a[k] + coeff_b[i][k] * b[k] + coeff_g[i][k]
860    // (tau[i][k+1] - tau[i][k]) * normalize[i] = coeff_a[i][k+1] * a[k+1] + coeff_b[i][k+1] * b[k+1] + coeff_g[i][k+1] - coeff_a[i][k] * a[k] - coeff_b[i][k] * b[k] - coeff_g[i][k]
861
862    let n_b_old = b.len();
863    // A*x-b = -s = -coeff_a[i][k] * x[k] + coeff_a[i][k+1] * x[k+1] - coeff_b[i][k] * x[n+k+1] + coeff_b[i][k+1] * x[n+k+2] + coeff_g[i][k+1] - coeff_g[i][k] - t[i][k] <= 0
864    // A*x-b = -s = -(-coeff_a[i][k] * x[k] + coeff_a[i][k+1] * x[k+1] - coeff_b[i][k] * x[n+k+1] + coeff_b[i][k+1] * x[n+k+2] + coeff_g[i][k+1] - coeff_g[i][k] - t[i][k]) <= 0
865    if num_stationary.0 > 0 {
866        // Consider (tau[i][num_stationary.0] - tau[i][0]) * normalize[i] = coeff_a[i][num_stationary.0] * a[num_stationary.0] + coeff_b[i][num_stationary.0] * b[num_stationary.0] + coeff_g[i][num_stationary.0] - coeff_g[i][0]
867        let col_a = coeff_a.column(num_stationary.0);
868        let col_b = coeff_b.column(num_stationary.0);
869        let col_g = coeff_g.column(num_stationary.0);
870        let col_g_0 = coeff_g.column(0);
871        let n_var_old = q_object.len();
872        for (i, (&v_a, &v_b, &v_g, &v_g_0)) in
873            izip!(col_a.iter(), col_b.iter(), col_g.iter(), col_g_0.iter())
874                .take(dim)
875                .enumerate()
876        {
877            // dtau_normal = v_a * a[num_stationary.0] + v_b * b[num_stationary.0] + v_g - v_g_0
878            // A*x-b = -s = v_a * a[num_stationary.0] + v_b * b[num_stationary.0] + v_g - v_g_0 - t[i] <= 0
879            row.resize(row.len() + 3, b.len());
880            col.extend([num_stationary.0, n + num_stationary.0 + 1, n_var_old + i]);
881            val.extend([v_a, v_b, -1.0]);
882            b.push(v_g - v_g_0);
883            // A*x-b = -s = -(v_a * a[num_stationary.0] + v_b * b[num_stationary.0] + v_g - v_g_0) - t[i] <= 0
884            row.resize(row.len() + 3, b.len());
885            col.extend([num_stationary.0, n + num_stationary.0 + 1, n_var_old + i]);
886            val.extend([-v_a, -v_b, -1.0]);
887            b.push(v_g_0 - v_g);
888        }
889        q_object.resize(q_object.len() + dim, weight);
890    }
891
892    if num_stationary.1 > 0 {
893        // Consider (tau[i][n-num_stationary.1] - tau[i][n]) * normalize[i] = coeff_a[i][n-num_stationary.1] * a[n-num_stationary.1] + coeff_b[i][n-num_stationary.1] * b[n-num_stationary.1] + coeff_g[i][n-num_stationary.1] - coeff_g[i][n]
894        let col_a = coeff_a.column(n - num_stationary.1);
895        let col_b = coeff_b.column(n - num_stationary.1);
896        let col_g = coeff_g.column(n - num_stationary.1);
897        let col_g_f = coeff_g.column(n);
898        let n_var_old = q_object.len();
899        for (i, (&v_a, &v_b, &v_g, &v_g_f)) in
900            izip!(col_a.iter(), col_b.iter(), col_g.iter(), col_g_f.iter())
901                .take(dim)
902                .enumerate()
903        {
904            // dtau_normal = v_a * a[n-num_stationary.1] + v_b * b[n-num_stationary.1] + v_g - v_g_0
905            // A*x-b = -s = v_a * a[n-num_stationary.1] + v_b * b[n-num_stationary.1] + v_g - v_g_0 - t[i] <= 0
906            row.resize(row.len() + 3, b.len());
907            col.extend([
908                n - num_stationary.1,
909                n + n - num_stationary.1 + 1,
910                n_var_old + i,
911            ]);
912            val.extend([v_a, v_b, -1.0]);
913            b.push(v_g - v_g_f);
914            // A*x-b = -s = -(v_a * a[n-num_stationary.1] + v_b * b[n-num_stationary.1] + v_g - v_g_f) - t[i] <= 0
915            row.resize(row.len() + 3, b.len());
916            col.extend([
917                n - num_stationary.1,
918                n + n - num_stationary.1 + 1,
919                n_var_old + i,
920            ]);
921            val.extend([-v_a, -v_b, -1.0]);
922            b.push(v_g_f - v_g);
923        }
924        q_object.resize(q_object.len() + dim, weight);
925    }
926
927    // Consider (tau[i][k+1] - tau[i][k]) * normalize[i] for k in num_stationary.0..(n-num_stationary.1)
928    for (k, ((col_a_curr, col_b_curr, col_g_curr), (col_a_next, col_b_next, col_g_next))) in izip!(
929        coeff_a.column_iter(),
930        coeff_b.column_iter(),
931        coeff_g.column_iter()
932    )
933    .tuple_windows()
934    .enumerate()
935    .skip(num_stationary.0)
936    .take(n - num_stationary.0 - num_stationary.1)
937    {
938        let n_var_old = q_object.len();
939        for (i, (&v_a_curr, &v_b_curr, &v_g_curr, &v_a_next, &v_b_next, &v_g_next)) in izip!(
940            col_a_curr.iter(),
941            col_b_curr.iter(),
942            col_g_curr.iter(),
943            col_a_next.iter(),
944            col_b_next.iter(),
945            col_g_next.iter()
946        )
947        .enumerate()
948        {
949            // dtau_normal[i] = -v_a_curr * a[k] + v_a_next * a[k+1] - v_b_curr * b[k] + v_b_next * b[k+1] + v_g_next- v_g_curr
950            // A*x-b = -s = -v_a_curr * a[k] + v_a_next * a[k+1] - v_b_curr * b[k] + v_b_next * b[k+1] + v_g_next - v_g_curr - t[i][k] <= 0
951            row.resize(row.len() + 5, b.len());
952            col.extend([k, k + 1, n + k + 1, n + k + 2, n_var_old + i]);
953            val.extend([-v_a_curr, v_a_next, -v_b_curr, v_b_next, -1.0]);
954            b.push(v_g_next - v_g_curr);
955            // A*x-b = -s = -(-v_a_curr * a[k] + v_a_next * a[k+1] - v_b_curr * b[k] + v_b_next * b[k+1] + v_g_next - v_g_curr) - t[i][k] <= 0
956            row.resize(row.len() + 5, b.len());
957            col.extend([k, k + 1, n + k + 1, n + k + 2, n_var_old + i]);
958            val.extend([v_a_curr, -v_a_next, v_b_curr, -v_b_next, -1.0]);
959            b.push(v_g_curr - v_g_next);
960        }
961        q_object.resize(q_object.len() + dim, weight);
962    }
963    cones.push(NonnegativeConeT(b.len() - n_b_old));
964
965    true
966}
967
968/// Add the constraints and objective for Linear in COPP3 optimization.
969fn clarabel_objective_linear_copp3(
970    s: &[f64],
971    weight: f64,
972    alpha: &[f64],
973    beta: &[f64],
974    q_object: &mut [f64],
975    num_stationary: (usize, usize),
976) -> bool {
977    if alpha.len() != s.len() || beta.len() != s.len() {
978        return false;
979    }
980    let n = s.len() - 1;
981    // objective: minimize weight * \sum (alpha[k]*a[k] + beta[k]*b[k])
982    if num_stationary.0 > 1 {
983        let &s_start = s.first().unwrap();
984        let ds_start = s[num_stationary.0] - s_start;
985        let q_n1 = &mut q_object[num_stationary.0];
986        for (&s_k, &alpha_k, &beta_k) in izip!(s.iter(), alpha.iter(), beta.iter())
987            .skip(1)
988            .take(num_stationary.0 - 1)
989        {
990            let dsk_start = s_k - s_start;
991            let gamma = dsk_start / ds_start;
992            // a_k = a[num_stationary.0] * gamma;
993            // b_k = a[num_stationary.0] * gamma / (1.5 * dsk_start);
994            *q_n1 += weight * gamma * gamma.cbrt() * (alpha_k + beta_k / (1.5 * dsk_start));
995        }
996    }
997    if num_stationary.1 > 1 {
998        let &s_final = s.last().unwrap();
999        let ds_final = s[n - num_stationary.1] - s_final;
1000        let q_n2 = &mut q_object[n - num_stationary.1];
1001        for (&s_k, &alpha_k, &beta_k) in izip!(s.iter(), alpha.iter(), beta.iter())
1002            .skip(1)
1003            .take(num_stationary.1 - 1)
1004        {
1005            let dsk_final = s_k - s_final;
1006            let gamma = dsk_final / ds_final;
1007            // a_k = a[n - num_stationary.1] * gamma;
1008            // b_k = a[n - num_stationary.1] * gamma / (1.5 * dsk_start);
1009            *q_n2 += weight * gamma * gamma.cbrt() * (alpha_k + beta_k / (1.5 * dsk_final));
1010        }
1011    }
1012    for (q_k, &alpha_k) in q_object
1013        .iter_mut()
1014        .zip(alpha.iter())
1015        .take(n + 1 - num_stationary.1)
1016        .skip(num_stationary.0)
1017    {
1018        *q_k += weight * alpha_k;
1019    }
1020    for (q_k, &beta_k) in q_object
1021        .iter_mut()
1022        .skip(n + 1)
1023        .zip(beta.iter())
1024        .take(n + 1 - num_stationary.1)
1025        .skip(num_stationary.0)
1026    {
1027        *q_k += weight * beta_k;
1028    }
1029
1030    true
1031}
1032
1033/// Compute the time value in COPP3 optimization.  
1034/// Input: a_sqrt_down = 1 / sqrt(a)  
1035#[cfg(test)]
1036#[inline(always)]
1037fn objective_value_time_copp3(
1038    a_sqrt_down: &[f64],
1039    weight_a: &[f64],
1040    num_stationary: (usize, usize),
1041) -> f64 {
1042    let n = a_sqrt_down.len() - 1;
1043    let k_skip = skip_a_for_xi(num_stationary.0);
1044    let len_xi = length_xi(n, num_stationary);
1045    // objective: minimize \sum  weight_a[k] / sqrt(a[k])
1046    let mut objective = 0.0;
1047    for (a_sqrt_down, weight_a) in a_sqrt_down
1048        .iter()
1049        .zip(weight_a.iter())
1050        .skip(k_skip)
1051        .take(len_xi)
1052    {
1053        objective += weight_a * a_sqrt_down;
1054    }
1055    objective
1056}
1057
1058/// Compute the thermal energy value in COPP3 optimization.
1059#[cfg(test)]
1060#[inline(always)]
1061fn objective_value_thermal_energy_copp3(
1062    a_sqrt_down: &[f64],
1063    weight_a: &[f64],
1064    num_stationary: (usize, usize),
1065    torque: &DMatrix<f64>,
1066    normalize: &[f64],
1067) -> f64 {
1068    let mut objective = 0.0;
1069    let n = a_sqrt_down.len() - 1;
1070    if num_stationary.0 > 0 {
1071        // minmize: weight_a[0] / sqrt(a[num_stationary.0]) * \sum[i] { (tau_average[i] * normalize[i])^2 }
1072        // tau_average[i] = (tau_normal[i][0] + tau_normal[i][num_stationary.0]) / 2
1073        let torque_n1 = torque.column(num_stationary.0);
1074        let torque_0 = torque.column(0);
1075        let weight = weight_a[0] * a_sqrt_down[num_stationary.0];
1076        for (tau_n1, tau_0, &normalize_i) in
1077            izip!(torque_n1.iter(), torque_0.iter(), normalize.iter())
1078        {
1079            let tau_average = 0.5 * normalize_i * (tau_n1 + tau_0);
1080            objective += weight * tau_average * tau_average;
1081        }
1082    }
1083    if num_stationary.1 > 0 {
1084        // minmize: weight_a[n] / sqrt(a[n-num_stationary.1]) * \sum[i] { (tau_average[i] * normalize[i])^2 }
1085        // tau_average[i] = (tau_normal[i][n] + tau_normal[i][n-num_stationary.1]) / 2
1086        let torque_n2 = torque.column(n - num_stationary.1);
1087        let torque_n = torque.column(n);
1088        let weight = weight_a[n] * a_sqrt_down[n - num_stationary.1];
1089        for (tau_n2, tau_n, &normalize_i) in
1090            izip!(torque_n2.iter(), torque_n.iter(), normalize.iter())
1091        {
1092            let tau_average = 0.5 * normalize_i * (tau_n2 + tau_n);
1093            objective += weight * tau_average * tau_average;
1094        }
1095    }
1096    let k_skip = skip_a_for_xi(num_stationary.0);
1097    let len_xi = length_xi(n, num_stationary);
1098    for (torque_k, &a_sqrt_down_k, &weight_a_k) in
1099        izip!(torque.column_iter(), a_sqrt_down.iter(), weight_a.iter())
1100            .skip(k_skip)
1101            .take(len_xi)
1102    {
1103        for (tau_k, &normalize_i) in torque_k.iter().zip(normalize.iter()) {
1104            let tau_normal = tau_k * normalize_i;
1105            objective += 4.0 * weight_a_k * tau_normal * tau_normal * a_sqrt_down_k;
1106        }
1107    }
1108
1109    objective
1110}
1111
1112/// Compute the thermal energy value in COPP3 optimization.
1113#[cfg(test)]
1114#[inline(always)]
1115fn objective_value_tv_torque_copp3(
1116    torque: &DMatrix<f64>,
1117    normalize: &[f64],
1118    num_stationary: (usize, usize),
1119) -> f64 {
1120    let mut objective = 0.0;
1121    let n = torque.ncols() - 1;
1122    if num_stationary.0 > 0 {
1123        // |tau[i][num_stationary.0] - tau[i][0]| * normalize[i]
1124        let torque_n1 = torque.column(num_stationary.0);
1125        let torque_0 = torque.column(0);
1126        for (tau_n1, tau_0, &normalize_i) in
1127            izip!(torque_n1.iter(), torque_0.iter(), normalize.iter())
1128        {
1129            objective += normalize_i * (tau_n1 - tau_0).abs();
1130        }
1131    }
1132    if num_stationary.1 > 0 {
1133        // |tau[i][n-num_stationary.1] - tau[i][n]| * normalize[i]
1134        let torque_n2 = torque.column(n - num_stationary.1);
1135        let torque_n = torque.column(n);
1136        for (tau_n2, tau_n, &normalize_i) in
1137            izip!(torque_n2.iter(), torque_n.iter(), normalize.iter())
1138        {
1139            objective += normalize_i * (tau_n2 - tau_n).abs();
1140        }
1141    }
1142    // Consider (tau[i][k+1] - tau[i][k]) * normalize[i] for k in num_stationary.0..(n-num_stationary.1)
1143    for (torque_col_curr, torque_col_next) in torque
1144        .column_iter()
1145        .tuple_windows()
1146        .skip(num_stationary.0)
1147        .take(n - num_stationary.0 - num_stationary.1)
1148    {
1149        for (tau_k, tau_k_next, &normalize_i) in izip!(
1150            torque_col_curr.iter(),
1151            torque_col_next.iter(),
1152            normalize.iter()
1153        ) {
1154            objective += normalize_i * (tau_k_next - tau_k).abs();
1155        }
1156    }
1157
1158    objective
1159}
1160
1161/// Compute the objective value for Linear in COPP3 optimization.
1162#[cfg(test)]
1163#[inline(always)]
1164fn objective_value_linear_copp3(
1165    a_profile: &[f64],
1166    b_profile: &[f64],
1167    alpha: &[f64],
1168    beta: &[f64],
1169) -> f64 {
1170    // objective: minimize \sum (alpha[k]*a[k] + beta[k]*b[k])
1171    let mut objective = 0.0;
1172    for (a_curr, alpha_curr) in a_profile.iter().zip(alpha.iter()) {
1173        // alpha[k]*a[k]
1174        objective += a_curr * alpha_curr;
1175    }
1176    for (b_curr, beta_curr) in b_profile.iter().zip(beta.iter()) {
1177        // beta[k]*b[k]
1178        objective += b_curr * beta_curr;
1179    }
1180    objective
1181}
1182
1183/// Compute the objective value for COPP3 optimization.
1184#[cfg(test)]
1185pub(crate) fn objective_value_copp3_opt<M: RobotTorque>(
1186    problem: &Copp3Problem<M>,
1187    a_profile: &[f64],
1188    b_profile: &[f64],
1189    num_stationary: (usize, usize),
1190) -> (f64, Vec<f64>) {
1191    let s = problem
1192        .robot
1193        .constraints
1194        .s_vec(problem.idx_s_start, problem.idx_s_start + a_profile.len());
1195    let Ok(s) = s else {
1196        return (f64::INFINITY, vec![0.0; problem.objectives.len()]);
1197    };
1198    if a_profile.len() != s.len() || b_profile.len() != s.len() {
1199        return (f64::INFINITY, vec![0.0; problem.objectives.len()]);
1200    }
1201    let (a_sqrt_down, weight_a_time) = if problem.objectives.iter().any(|obj| {
1202        matches!(
1203            obj,
1204            CoppObjective::Time(_) | CoppObjective::ThermalEnergy(_, _)
1205        )
1206    }) {
1207        (
1208            a_profile
1209                .iter()
1210                .map(|a| 1.0 / a.sqrt().max(1E-16))
1211                .collect(),
1212            get_weight_a_topp3(&s, num_stationary),
1213        )
1214    } else {
1215        (vec![], vec![])
1216    };
1217    let weight_a_torque = if problem
1218        .objectives
1219        .iter()
1220        .any(|obj| matches!(obj, CoppObjective::ThermalEnergy(_, _)))
1221    {
1222        get_weight_a_copp3(&s, num_stationary)
1223    } else {
1224        vec![]
1225    };
1226    let torque = if problem.objectives.iter().any(|obj| {
1227        matches!(
1228            obj,
1229            CoppObjective::ThermalEnergy(_, _) | CoppObjective::TotalVariationTorque(_, _)
1230        )
1231    }) {
1232        let torque_result =
1233            problem
1234                .robot
1235                .get_torque_with_ab(a_profile, b_profile, problem.idx_s_start);
1236        match torque_result {
1237            Ok(torque) => torque,
1238            _ => return (f64::INFINITY, vec![0.0; problem.objectives.len()]),
1239        }
1240    } else {
1241        DMatrix::<f64>::zeros(0, 0)
1242    };
1243    let mut obj_val = Vec::with_capacity(problem.objectives.len());
1244    let mut obj_val_total = 0.0;
1245    for obj in problem.objectives {
1246        match obj {
1247            CoppObjective::Time(weight) => {
1248                let obj_here =
1249                    objective_value_time_copp3(&a_sqrt_down, &weight_a_time, num_stationary);
1250                obj_val.push(obj_here);
1251                obj_val_total += weight * obj_here;
1252            }
1253            CoppObjective::ThermalEnergy(weight, normalize) => {
1254                let obj_here = objective_value_thermal_energy_copp3(
1255                    &a_sqrt_down,
1256                    &weight_a_torque,
1257                    num_stationary,
1258                    &torque,
1259                    normalize,
1260                );
1261                obj_val.push(obj_here);
1262                obj_val_total += weight * obj_here;
1263            }
1264            CoppObjective::TotalVariationTorque(weight, normalize) => {
1265                let obj_here = objective_value_tv_torque_copp3(&torque, normalize, num_stationary);
1266                obj_val.push(obj_here);
1267                obj_val_total += weight * obj_here;
1268            }
1269            CoppObjective::Linear(weight, alpha, beta) => {
1270                let obj_here = objective_value_linear_copp3(a_profile, b_profile, alpha, beta);
1271                obj_val.push(obj_here);
1272                obj_val_total += weight * obj_here;
1273            }
1274        }
1275    }
1276    (obj_val_total, obj_val)
1277}
1278
1279#[cfg(test)]
1280mod tests {
1281    use super::*;
1282    use crate::copp::copp2::stable::basic::{Topp2ProblemBuilder, s_to_t_topp2};
1283    use crate::copp::copp2::stable::reach_set2::ReachSet2OptionsBuilder;
1284    use crate::copp::copp2::stable::topp2_ra::topp2_ra;
1285    use crate::copp::copp3::stable::basic::{Copp3ProblemBuilder, s_to_t_topp3};
1286    use crate::copp::copp3::stable::topp3_lp::topp3_lp;
1287    use crate::copp::copp3::stable::topp3_socp::topp3_socp;
1288    use crate::copp::{ClarabelOptionsBuilder, default_clarabel_settings};
1289    use crate::path::{add_symmetric_axial_limits_for_test, lissajous_path_for_test};
1290    use crate::robot::robot_core::Robot;
1291    use std::time::Instant;
1292    use std::vec;
1293
1294    #[test]
1295    fn test_copp3_lp() -> Result<(), CoppError> {
1296        run_test_copp3_lp_repeated(1, false)
1297    }
1298
1299    /// Conditions: release, --include-ignored, CPU = Intel(R) Core(TM) Ultra 9 285K.
1300    /// Average 100 experiments: tc_ra = 0.3270 ms, tc_lp = 237.4646 ms, tc_copp = 232.0414 ms, tf_ra = 6.304760, tf_lp = 6.524883, tf_copp = 6.524883, obj_lp = -0.031621, obj_copp = -0.031621
1301    #[test]
1302    #[ignore = "slow"]
1303    fn test_copp3_lp_robust() -> Result<(), CoppError> {
1304        run_test_copp3_lp_repeated(100, true)
1305    }
1306
1307    #[test]
1308    fn test_copp3_qp() -> Result<(), CoppError> {
1309        run_test_copp3_qp_repeated(1, false)
1310    }
1311
1312    /// Conditions: release, --include-ignored, CPU = Intel(R) Core(TM) Ultra 9 285K.
1313    /// Average 100 experiments (fail 0): tc_ra = 0.3452 ms, tc_qp = 329.8604 ms, tc_copp = 302.8725 ms, tf_ra = 6.122942, tf_qp = 6.342830, tf_copp = 6.342830, obj_qp = 6.348896, obj_copp = 6.348896
1314    #[test]
1315    #[ignore = "slow"]
1316    fn test_copp3_qp_robust() -> Result<(), CoppError> {
1317        run_test_copp3_qp_repeated(100, true)
1318    }
1319
1320    #[test]
1321    fn test_all_objectives() -> Result<(), CoppError> {
1322        run_test_all_objectives_repeated(1, false)
1323    }
1324
1325    /// Conditions: release, --include-ignored, CPU = Intel(R) Core(TM) Ultra 9 285K.
1326    /// Average 100 experiments (fail 0):
1327    /// Case 0: tc=301.449ms, obj=[6.355272621082504, 37.88484670012183, 30.538889633761094, 0.0014588588234334063]
1328    /// Case 1: tc=345.585ms, obj=[8.929951413267652, 11.232320784989641, 15.611002914308818, 0.001319403151583034]
1329    /// Case 2: tc=303.617ms, obj=[15.7679747735929, 2.0044043744079847, 5.695644764673545, 0.0010702913648672737]
1330    /// Case 3: tc=440.100ms, obj=[11.999657677994689, 5.6638102612967565, 5.999807538045939, 0.00022405699762245119]
1331    /// Case 4: tc=306.763ms, obj=[6.355272615415654, 37.884856482672014, 30.621301350094082, 0.0014588597553749254]
1332    #[test]
1333    #[ignore = "slow"]
1334    fn test_all_objectives_robust() -> Result<(), CoppError> {
1335        run_test_all_objectives_repeated(100, true)
1336    }
1337
1338    fn run_test_copp3_lp_repeated(n_exp: usize, flag_print_step: bool) -> Result<(), CoppError> {
1339        let mut tc_sum_ra = 0.0;
1340        let mut tc_sum_lp = 0.0;
1341        let mut tc_sum_copp = 0.0;
1342        let mut tf_sum_ra = 0.0;
1343        let mut tf_sum_lp = 0.0;
1344        let mut tf_sum_copp = 0.0;
1345        let mut obj_sum_lp = 0.0;
1346        let mut obj_sum_copp = 0.0;
1347
1348        for i_exp in 0..n_exp {
1349            let n: usize = 1000;
1350            let dim = 7;
1351            let mut robot = Robot::with_capacity(dim, n);
1352
1353            let mut rng = rand::rng();
1354            let (s, derivs, omega, phi) =
1355                lissajous_path_for_test(dim, n, &mut rng).expect("random range is valid");
1356            robot.with_s(&s.as_view())?;
1357            robot.with_q(
1358                &derivs.q.as_view(),
1359                &derivs.dq.as_ref().unwrap().as_view(),
1360                &derivs.ddq.as_ref().unwrap().as_view(),
1361                derivs.dddq.as_ref().map(|m| m.as_view()).as_ref(),
1362                0,
1363            )?;
1364            add_symmetric_axial_limits_for_test(&mut robot, 1.0, 1.0, Some(5.0))?;
1365
1366            let topp2_problem = Topp2ProblemBuilder::new(&robot, (0, n - 1), (0.0, 0.0)).build()?;
1367            // Step 1. Topp2-RA
1368            let start = Instant::now();
1369            let options_ra0 = ReachSet2OptionsBuilder::new()
1370                .lp_feas_tol(1E-9)
1371                .a_cmp_abs_tol(1E-9)
1372                .a_cmp_rel_tol(1E-9)
1373                .build()?;
1374            let a_ra0 = topp2_ra(&topp2_problem, &options_ra0)?;
1375            let tc_ra0 = start.elapsed().as_secs_f64() * 1E3;
1376            let (tf_ra0, _) = s_to_t_topp2(s.as_slice(), &a_ra0, 0.0);
1377
1378            let objectives = [CoppObjective::Linear(
1379                1.0,
1380                &get_weight_a_topp3(&s.as_slice()[0..n], (1, 1))
1381                    .iter()
1382                    .map(|&w_a| -w_a)
1383                    .collect_vec(),
1384                &vec![0.0; n],
1385            )];
1386            let copp3_problem = Copp3ProblemBuilder::new(
1387                &mut robot,
1388                &objectives,
1389                0,
1390                &a_ra0,
1391                (0.0, 0.0),
1392                (0.0, 0.0),
1393            )
1394            .build_with_linearization()?;
1395
1396            // Step 2. Test Copp3-SOCP
1397            let start = Instant::now();
1398            let (a_copp, b_copp, num_stationary_copp) = {
1399                let mut settings = default_clarabel_settings();
1400                settings.tol_gap_rel = 1E-6;
1401                let options = ClarabelOptionsBuilder::with_clarabel_setting(settings)
1402                    .allow_almost_solved(true)
1403                    .build()?;
1404                let (result, solution) = copp3_socp_expert(&copp3_problem, &options)?;
1405                if let Some(result) = result {
1406                    result
1407                } else {
1408                    return Err(CoppError::ClarabelSolverStatus(
1409                        "copp3_socp".into(),
1410                        solution.status,
1411                    ));
1412                }
1413            };
1414            let tc_copp = start.elapsed().as_secs_f64() * 1E3;
1415            // Test time profile generation
1416            let (tf_copp, _) =
1417                s_to_t_topp3(s.as_slice(), &a_copp, &b_copp, num_stationary_copp, 0.0);
1418
1419            // Step 3. Test Topp3-LP
1420            let options_lp = ClarabelOptionsBuilder::new()
1421                .allow_almost_solved(true)
1422                .build()?;
1423            let start = Instant::now();
1424            let (a_lp, b_lp, _) = topp3_lp(&copp3_problem.as_topp3_problem(), &options_lp)?;
1425            let tc_lp = start.elapsed().as_secs_f64() * 1E3;
1426            // Test time profile generation
1427            let (tf_lp, _) = s_to_t_topp3(s.as_slice(), &a_lp, &b_lp, num_stationary_copp, 0.0);
1428
1429            let (obj_lp, _) =
1430                objective_value_copp3_opt(&copp3_problem, &a_lp, &b_lp, num_stationary_copp);
1431            let (obj_copp, _) =
1432                objective_value_copp3_opt(&copp3_problem, &a_copp, &b_copp, num_stationary_copp);
1433
1434            if flag_print_step {
1435                crate::verbosity_log!(
1436                    crate::diag::Verbosity::Summary,
1437                    "Exp #{}: tc_ra = {:.4} ms, tc_lp = {:.4} ms, tc_copp = {:.4} ms, tf_ra = {:.6}, tf_lp = {:.6}, tf_copp = {:.6}, obj_lp = {:.6}, obj_copp = {:.6}",
1438                    i_exp + 1,
1439                    tc_ra0,
1440                    tc_lp,
1441                    tc_copp,
1442                    tf_ra0,
1443                    tf_lp,
1444                    tf_copp,
1445                    obj_lp,
1446                    obj_copp
1447                );
1448            }
1449
1450            if (tf_copp - tf_lp).abs() > 1e-8 {
1451                crate::verbosity_log!(
1452                    crate::diag::Verbosity::Summary,
1453                    "omega = {omega:?}\nphi = {phi:?}"
1454                );
1455                crate::verbosity_log!(
1456                    crate::diag::Verbosity::Debug,
1457                    "COPP3 time optimality failed! tf_copp - tf_lp = {}",
1458                    tf_copp - tf_lp
1459                );
1460            }
1461
1462            tc_sum_ra += tc_ra0;
1463            tc_sum_lp += tc_lp;
1464            tc_sum_copp += tc_copp;
1465            tf_sum_ra += tf_ra0;
1466            tf_sum_lp += tf_lp;
1467            tf_sum_copp += tf_copp;
1468            obj_sum_lp += obj_lp;
1469            obj_sum_copp += obj_copp;
1470        }
1471
1472        crate::verbosity_log!(
1473            crate::diag::Verbosity::Summary,
1474            "Average {} experiments: tc_ra = {:.4} ms, tc_lp = {:.4} ms, tc_copp = {:.4} ms, tf_ra = {:.6}, tf_lp = {:.6}, tf_copp = {:.6}, obj_lp = {:.6}, obj_copp = {:.6}",
1475            n_exp,
1476            tc_sum_ra / n_exp as f64,
1477            tc_sum_lp / n_exp as f64,
1478            tc_sum_copp / n_exp as f64,
1479            tf_sum_ra / n_exp as f64,
1480            tf_sum_lp / n_exp as f64,
1481            tf_sum_copp / n_exp as f64,
1482            obj_sum_lp / n_exp as f64,
1483            obj_sum_copp / n_exp as f64
1484        );
1485
1486        Ok(())
1487    }
1488
1489    fn run_test_copp3_qp_repeated(n_exp: usize, flag_print_step: bool) -> Result<(), CoppError> {
1490        let mut tc_sum_ra = 0.0;
1491        let mut tc_sum_qp = 0.0;
1492        let mut tc_sum_copp = 0.0;
1493        let mut tf_sum_ra = 0.0;
1494        let mut tf_sum_qp = 0.0;
1495        let mut tf_sum_copp = 0.0;
1496        let mut obj_sum_qp = 0.0;
1497        let mut obj_sum_copp = 0.0;
1498        let mut succeed = 0;
1499
1500        for i_exp in 0..n_exp {
1501            let n: usize = 1000;
1502            let dim = 7;
1503            let mut robot = Robot::with_capacity(dim, n);
1504
1505            let mut rng = rand::rng();
1506            let (s, derivs, omega, phi) =
1507                lissajous_path_for_test(dim, n, &mut rng).expect("random range is valid");
1508            robot.with_s(&s.as_view())?;
1509            robot.with_q(
1510                &derivs.q.as_view(),
1511                &derivs.dq.as_ref().unwrap().as_view(),
1512                &derivs.ddq.as_ref().unwrap().as_view(),
1513                derivs.dddq.as_ref().map(|m| m.as_view()).as_ref(),
1514                0,
1515            )?;
1516            add_symmetric_axial_limits_for_test(&mut robot, 1.0, 1.0, Some(5.0))?;
1517
1518            let topp2_problem = Topp2ProblemBuilder::new(&robot, (0, n - 1), (0.0, 0.0)).build()?;
1519            // Step 1. Topp2-RA
1520            let start = Instant::now();
1521            let options_ra0 = ReachSet2OptionsBuilder::new()
1522                .lp_feas_tol(1E-9)
1523                .a_cmp_abs_tol(1E-9)
1524                .a_cmp_rel_tol(1E-9)
1525                .build()?;
1526            let a_ra0 = topp2_ra(&topp2_problem, &options_ra0)?;
1527            let tc_ra0 = start.elapsed().as_secs_f64() * 1E3;
1528            let (tf_ra0, _) = s_to_t_topp2(s.as_slice(), &a_ra0, 0.0);
1529
1530            let objective = [CoppObjective::Time(1.0)];
1531            let copp3_problem =
1532                Copp3ProblemBuilder::new(&mut robot, &objective, 0, &a_ra0, (0.0, 0.0), (0.0, 0.0))
1533                    .build_with_linearization()?;
1534
1535            // Step 2. Test Copp3-SOCP
1536            let start = Instant::now();
1537            let (a_copp, b_copp, num_stationary_copp) = {
1538                let mut settings = default_clarabel_settings();
1539                settings.tol_gap_rel = 1E-6;
1540                let options = ClarabelOptionsBuilder::with_clarabel_setting(settings)
1541                    .allow_almost_solved(true)
1542                    .build()?;
1543                let (result, solution) = match copp3_socp_expert(&copp3_problem, &options) {
1544                    Ok(res) => res,
1545                    Err(_) => {
1546                        crate::verbosity_log!(
1547                            crate::diag::Verbosity::Debug,
1548                            "Exp #{}: Clarabel solver failed in copp3_socp_expert!",
1549                            i_exp + 1
1550                        );
1551                        continue;
1552                    }
1553                };
1554                if let Some(result) = result {
1555                    result
1556                } else {
1557                    return Err(CoppError::ClarabelSolverStatus(
1558                        "copp3_socp".into(),
1559                        solution.status,
1560                    ));
1561                }
1562            };
1563            let tc_copp = start.elapsed().as_secs_f64() * 1E3;
1564            // Test time profile generation
1565            let (tf_copp, _) =
1566                s_to_t_topp3(s.as_slice(), &a_copp, &b_copp, num_stationary_copp, 0.0);
1567
1568            // Step 3. Test Topp3-LP
1569            let start = Instant::now();
1570            let options_qp = ClarabelOptionsBuilder::new()
1571                .allow_almost_solved(true)
1572                .build()?;
1573            let (a_qp, b_qp, _) = topp3_socp(&copp3_problem.as_topp3_problem(), &options_qp)?;
1574            let tc_qp = start.elapsed().as_secs_f64() * 1E3;
1575            // Test time profile generation
1576            let (tf_qp, _) = s_to_t_topp3(s.as_slice(), &a_qp, &b_qp, num_stationary_copp, 0.0);
1577
1578            let (obj_qp, _) =
1579                objective_value_copp3_opt(&copp3_problem, &a_qp, &b_qp, num_stationary_copp);
1580            let (obj_copp, _) =
1581                objective_value_copp3_opt(&copp3_problem, &a_copp, &b_copp, num_stationary_copp);
1582
1583            if flag_print_step {
1584                crate::verbosity_log!(
1585                    crate::diag::Verbosity::Summary,
1586                    "Exp #{}: tc_ra = {:.4} ms, tc_qp = {:.4} ms, tc_copp = {:.4} ms, tf_ra = {:.6}, tf_qp = {:.6}, tf_copp = {:.6}, obj_qp = {:.6}, obj_copp = {:.6}",
1587                    i_exp + 1,
1588                    tc_ra0,
1589                    tc_qp,
1590                    tc_copp,
1591                    tf_ra0,
1592                    tf_qp,
1593                    tf_copp,
1594                    obj_qp,
1595                    obj_copp
1596                );
1597            }
1598
1599            if (tf_copp - tf_qp).abs() > 1e-4 || (obj_copp - obj_qp).abs() > 1e-4 {
1600                crate::verbosity_log!(
1601                    crate::diag::Verbosity::Summary,
1602                    "omega = {omega:?}\nphi = {phi:?}"
1603                );
1604                crate::verbosity_log!(
1605                    crate::diag::Verbosity::Debug,
1606                    "COPP3 time optimality failed at Exp #{}! tf_copp - tf_qp = {}, obj_copp - obj_qp = {}",
1607                    i_exp + 1,
1608                    tf_copp - tf_qp,
1609                    obj_copp - obj_qp
1610                );
1611            }
1612
1613            tc_sum_ra += tc_ra0;
1614            tc_sum_qp += tc_qp;
1615            tc_sum_copp += tc_copp;
1616            tf_sum_ra += tf_ra0;
1617            tf_sum_qp += tf_qp;
1618            tf_sum_copp += tf_copp;
1619            obj_sum_qp += obj_qp;
1620            obj_sum_copp += obj_copp;
1621            succeed += 1;
1622        }
1623
1624        crate::verbosity_log!(
1625            crate::diag::Verbosity::Summary,
1626            "Average {n_exp} experiments (fail {}): tc_ra = {:.4} ms, tc_qp = {:.4} ms, tc_copp = {:.4} ms, tf_ra = {:.6}, tf_qp = {:.6}, tf_copp = {:.6}, obj_qp = {:.6}, obj_copp = {:.6}",
1627            n_exp - succeed,
1628            tc_sum_ra / succeed as f64,
1629            tc_sum_qp / succeed as f64,
1630            tc_sum_copp / succeed as f64,
1631            tf_sum_ra / succeed as f64,
1632            tf_sum_qp / succeed as f64,
1633            tf_sum_copp / succeed as f64,
1634            obj_sum_qp / succeed as f64,
1635            obj_sum_copp / succeed as f64
1636        );
1637
1638        Ok(())
1639    }
1640
1641    fn run_test_all_objectives_repeated(
1642        n_exp: usize,
1643        flag_print_step: bool,
1644    ) -> Result<(), CoppError> {
1645        let mut tc_sum_case0 = 0.0;
1646        let mut tc_sum_case1 = 0.0;
1647        let mut tc_sum_case2 = 0.0;
1648        let mut tc_sum_case3 = 0.0;
1649        let mut tc_sum_case4 = 0.0;
1650        let mut obj_sum_case0 = vec![0.0; 4];
1651        let mut obj_sum_case1 = vec![0.0; 4];
1652        let mut obj_sum_case2 = vec![0.0; 4];
1653        let mut obj_sum_case3 = vec![0.0; 4];
1654        let mut obj_sum_case4 = vec![0.0; 4];
1655
1656        let mut succeed = 0;
1657
1658        for i_exp in 0..n_exp {
1659            let n: usize = 1000;
1660            let dim = 7;
1661            let mut robot = Robot::with_capacity(dim, n);
1662
1663            let mut rng = rand::rng();
1664            let (s, derivs, omega, phi) =
1665                lissajous_path_for_test(dim, n, &mut rng).expect("random range is valid");
1666            // let omega: Vec<f64> = [
1667            //     4.723299430689122,
1668            //     1.2937933921386273,
1669            //     3.163544832429868,
1670            //     2.554983998551911,
1671            //     3.1752440065420076,
1672            //     6.036677287860053,
1673            //     5.4585988904049145,
1674            // ]
1675            // .into();
1676            // let phi: Vec<f64> = [
1677            //     0.3140306040123813,
1678            //     5.341058950177041,
1679            //     4.076168494444343,
1680            //     5.801150775658696,
1681            //     2.306341572703676,
1682            //     0.2550634807632447,
1683            //     3.986342358667442,
1684            // ]
1685            // .into();
1686
1687            if flag_print_step {
1688                crate::verbosity_log!(
1689                    crate::diag::Verbosity::Summary,
1690                    "omega = {omega:?}\nphi = {phi:?}"
1691                );
1692            }
1693            robot.with_s(&s.as_view())?;
1694            robot.with_q(
1695                &derivs.q.as_view(),
1696                &derivs.dq.as_ref().unwrap().as_view(),
1697                &derivs.ddq.as_ref().unwrap().as_view(),
1698                derivs.dddq.as_ref().map(|m| m.as_view()).as_ref(),
1699                0,
1700            )?;
1701            add_symmetric_axial_limits_for_test(&mut robot, 1.0, 1.0, Some(5.0))?;
1702
1703            let topp2_problem = Topp2ProblemBuilder::new(&robot, (0, n - 1), (0.0, 0.0)).build()?;
1704            // Step 1. Topp2-RA
1705            let options_ra0 = ReachSet2OptionsBuilder::new()
1706                .lp_feas_tol(1E-9)
1707                .a_cmp_abs_tol(1E-9)
1708                .a_cmp_rel_tol(1E-9)
1709                .build()?;
1710            let a_ra0 = topp2_ra(&topp2_problem, &options_ra0)?;
1711
1712            // Test different objectives in COPP2 optimization
1713            let objectives_test = [
1714                CoppObjective::Time(1.0),
1715                CoppObjective::ThermalEnergy(1.0, &vec![1.0; dim]),
1716                CoppObjective::TotalVariationTorque(1.0, &vec![1.0; dim]),
1717                CoppObjective::Linear(1.0, &vec![0.0; n], &vec![1.0; n - 1]),
1718            ];
1719
1720            // Case 0: Time only
1721            let obj_case0_src = [CoppObjective::Time(1.0)];
1722            let start = Instant::now();
1723            let (a_case0, b_case0, num_stationary) = {
1724                let copp3_problem = Copp3ProblemBuilder::new(
1725                    &mut robot,
1726                    &obj_case0_src,
1727                    0,
1728                    &a_ra0,
1729                    (0.0, 0.0),
1730                    (0.0, 0.0),
1731                )
1732                .build_with_linearization()?;
1733                let mut settings = default_clarabel_settings();
1734                settings.tol_gap_rel = 1E-6;
1735                let options = ClarabelOptionsBuilder::with_clarabel_setting(settings)
1736                    .allow_almost_solved(true)
1737                    .build()?;
1738                let (result, solution) = copp3_socp_expert(&copp3_problem, &options)?;
1739                if let Some(result) = result {
1740                    result
1741                } else {
1742                    crate::verbosity_log!(
1743                        crate::diag::Verbosity::Debug,
1744                        "{:?}",
1745                        CoppError::ClarabelSolverStatus(
1746                            "copp3_socp (case 0)".into(),
1747                            solution.status,
1748                        )
1749                    );
1750                    continue;
1751                }
1752            };
1753            let tc_copp3_case0 = start.elapsed().as_secs_f64() * 1E3;
1754            let (_, obj_case0) = {
1755                let copp3_problem = Copp3ProblemBuilder::new(
1756                    &mut robot,
1757                    &objectives_test,
1758                    0,
1759                    &a_ra0,
1760                    (0.0, 0.0),
1761                    (0.0, 0.0),
1762                )
1763                .build_with_linearization()?;
1764                objective_value_copp3_opt(&copp3_problem, &a_case0, &b_case0, num_stationary)
1765            };
1766
1767            // Case 1: Time and ThermalEnergy
1768            let obj_case1 = [
1769                CoppObjective::Time(1.0),
1770                CoppObjective::ThermalEnergy(1.0, &vec![1.0; dim]),
1771            ];
1772            let start = Instant::now();
1773            let (a_case1, b_case1, num_stationary) = {
1774                let copp3_problem = Copp3ProblemBuilder::new(
1775                    &mut robot,
1776                    &obj_case1,
1777                    0,
1778                    &a_ra0,
1779                    (0.0, 0.0),
1780                    (0.0, 0.0),
1781                )
1782                .build_with_linearization()?;
1783                let mut settings = default_clarabel_settings();
1784                settings.tol_gap_rel = 1E-6;
1785                let options = ClarabelOptionsBuilder::with_clarabel_setting(settings)
1786                    .allow_almost_solved(true)
1787                    .build()?;
1788                let (result, solution) = copp3_socp_expert(&copp3_problem, &options)?;
1789                if let Some(result) = result {
1790                    result
1791                } else {
1792                    crate::verbosity_log!(
1793                        crate::diag::Verbosity::Debug,
1794                        "{:?}",
1795                        CoppError::ClarabelSolverStatus(
1796                            "copp3_socp (case 1)".into(),
1797                            solution.status,
1798                        )
1799                    );
1800                    continue;
1801                }
1802            };
1803            let tc_copp3_case1 = start.elapsed().as_secs_f64() * 1E3;
1804            let (_, obj_case1) = {
1805                let copp3_problem = Copp3ProblemBuilder::new(
1806                    &mut robot,
1807                    &objectives_test,
1808                    0,
1809                    &a_ra0,
1810                    (0.0, 0.0),
1811                    (0.0, 0.0),
1812                )
1813                .build_with_linearization()?;
1814                objective_value_copp3_opt(&copp3_problem, &a_case1, &b_case1, num_stationary)
1815            };
1816            if obj_case1[0] < obj_case0[0] - 1E-3 || obj_case1[1] - 1E-3 > obj_case0[1] {
1817                let (tf_case0, _) = s_to_t_topp2(s.as_slice(), &a_case0, 0.0);
1818                let (tf_case1, _) = s_to_t_topp2(s.as_slice(), &a_case1, 0.0);
1819                crate::verbosity_log!(
1820                    crate::diag::Verbosity::Summary,
1821                    "omega = {omega:?}\nphi = {phi:?}"
1822                );
1823                crate::verbosity_log!(
1824                    crate::diag::Verbosity::Summary,
1825                    "Case 0: obj_time = {}, obj_thermal_energy = {}, tf = {}",
1826                    obj_case0[0],
1827                    obj_case0[1],
1828                    tf_case0
1829                );
1830                crate::verbosity_log!(
1831                    crate::diag::Verbosity::Summary,
1832                    "Case 1: obj_time = {}, obj_thermal_energy = {}, tf = {}",
1833                    obj_case1[0],
1834                    obj_case1[1],
1835                    tf_case1
1836                );
1837                crate::verbosity_log!(
1838                    crate::diag::Verbosity::Summary,
1839                    "Interesting... Cases 0 and 1"
1840                );
1841            }
1842
1843            // Case 2: Time and More ThermalEnergy
1844            let obj_case2 = [
1845                CoppObjective::Time(1.0),
1846                CoppObjective::ThermalEnergy(10.0, &vec![1.0; dim]),
1847            ];
1848            let start = Instant::now();
1849            let (a_case2, b_case2, num_stationary) = {
1850                let copp3_problem = Copp3ProblemBuilder::new(
1851                    &mut robot,
1852                    &obj_case2,
1853                    0,
1854                    &a_ra0,
1855                    (0.0, 0.0),
1856                    (0.0, 0.0),
1857                )
1858                .build_with_linearization()?;
1859                let mut settings = default_clarabel_settings();
1860                settings.tol_gap_rel = 1E-6;
1861                let options = ClarabelOptionsBuilder::with_clarabel_setting(settings)
1862                    .allow_almost_solved(true)
1863                    .build()?;
1864                let (result, solution) = copp3_socp_expert(&copp3_problem, &options)?;
1865                if let Some(result) = result {
1866                    result
1867                } else {
1868                    crate::verbosity_log!(
1869                        crate::diag::Verbosity::Debug,
1870                        "{:?}",
1871                        CoppError::ClarabelSolverStatus(
1872                            "copp3_socp (case 2)".into(),
1873                            solution.status,
1874                        )
1875                    );
1876                    continue;
1877                }
1878            };
1879            let tc_copp3_case2 = start.elapsed().as_secs_f64() * 1E3;
1880            let (_, obj_case2) = {
1881                let copp3_problem = Copp3ProblemBuilder::new(
1882                    &mut robot,
1883                    &objectives_test,
1884                    0,
1885                    &a_ra0,
1886                    (0.0, 0.0),
1887                    (0.0, 0.0),
1888                )
1889                .build_with_linearization()?;
1890                objective_value_copp3_opt(&copp3_problem, &a_case2, &b_case2, num_stationary)
1891            };
1892            if obj_case2[0] < obj_case1[0] - 1E-3 || obj_case2[1] - 1E-3 > obj_case1[1] {
1893                crate::verbosity_log!(
1894                    crate::diag::Verbosity::Summary,
1895                    "omega = {omega:?}\nphi = {phi:?}"
1896                );
1897                crate::verbosity_log!(
1898                    crate::diag::Verbosity::Summary,
1899                    "Case 1: obj_time = {}, obj_thermal_energy = {}",
1900                    obj_case1[0],
1901                    obj_case1[1]
1902                );
1903                crate::verbosity_log!(
1904                    crate::diag::Verbosity::Summary,
1905                    "Case 2: obj_time = {}, obj_thermal_energy = {}",
1906                    obj_case2[0],
1907                    obj_case2[1]
1908                );
1909                crate::verbosity_log!(
1910                    crate::diag::Verbosity::Summary,
1911                    "Interesting... Cases 1 and 2"
1912                );
1913            }
1914
1915            // Case 3: Time and TotalVariationTorque
1916            let obj_case3 = [
1917                CoppObjective::Time(1.0),
1918                CoppObjective::TotalVariationTorque(1.0, &vec![1.0; dim]),
1919            ];
1920            let start = Instant::now();
1921            let (a_case3, b_case3, num_stationary) = {
1922                let copp3_problem = Copp3ProblemBuilder::new(
1923                    &mut robot,
1924                    &obj_case3,
1925                    0,
1926                    &a_ra0,
1927                    (0.0, 0.0),
1928                    (0.0, 0.0),
1929                )
1930                .build_with_linearization()?;
1931                let mut settings = default_clarabel_settings();
1932                settings.tol_gap_rel = 1E-6;
1933                let options = ClarabelOptionsBuilder::with_clarabel_setting(settings)
1934                    .allow_almost_solved(true)
1935                    .build()?;
1936                let (result, solution) = copp3_socp_expert(&copp3_problem, &options)?;
1937                if let Some(result) = result {
1938                    result
1939                } else {
1940                    crate::verbosity_log!(
1941                        crate::diag::Verbosity::Debug,
1942                        "{:?}",
1943                        CoppError::ClarabelSolverStatus(
1944                            "copp3_socp (case 3)".into(),
1945                            solution.status,
1946                        )
1947                    );
1948                    continue;
1949                }
1950            };
1951            let tc_copp3_case3 = start.elapsed().as_secs_f64() * 1E3;
1952            let (_, obj_case3) = {
1953                let copp3_problem = Copp3ProblemBuilder::new(
1954                    &mut robot,
1955                    &objectives_test,
1956                    0,
1957                    &a_ra0,
1958                    (0.0, 0.0),
1959                    (0.0, 0.0),
1960                )
1961                .build_with_linearization()?;
1962                objective_value_copp3_opt(&copp3_problem, &a_case3, &b_case3, num_stationary)
1963            };
1964            if obj_case3[1] < obj_case1[1] - 1E-3 || obj_case3[2] - 1E-3 > obj_case1[2] {
1965                crate::verbosity_log!(
1966                    crate::diag::Verbosity::Summary,
1967                    "omega = {omega:?}\nphi = {phi:?}"
1968                );
1969                crate::verbosity_log!(
1970                    crate::diag::Verbosity::Summary,
1971                    "Case 1: obj_time = {}, obj_thermal_energy = {}, obj_total_variation_torque = {}",
1972                    obj_case1[0],
1973                    obj_case1[1],
1974                    obj_case1[2]
1975                );
1976                crate::verbosity_log!(
1977                    crate::diag::Verbosity::Summary,
1978                    "Case 3: obj_time = {}, obj_thermal_energy = {}, obj_total_variation_torque = {}",
1979                    obj_case3[0],
1980                    obj_case3[1],
1981                    obj_case3[2]
1982                );
1983                crate::verbosity_log!(
1984                    crate::diag::Verbosity::Summary,
1985                    "Interesting... Cases 1 and 3"
1986                );
1987            }
1988
1989            // Case 4: Time and Linear
1990            let obj_case4 = [
1991                CoppObjective::Time(1.0),
1992                CoppObjective::Linear(1.0, &vec![0.0; n], &vec![1.0; n]),
1993            ];
1994            let start = Instant::now();
1995            let (a_case4, b_case4, num_stationary) = {
1996                let copp3_problem = Copp3ProblemBuilder::new(
1997                    &mut robot,
1998                    &obj_case4,
1999                    0,
2000                    &a_ra0,
2001                    (0.0, 0.0),
2002                    (0.0, 0.0),
2003                )
2004                .build_with_linearization()?;
2005                let mut settings = default_clarabel_settings();
2006                settings.tol_gap_rel = 1E-6;
2007                let options = ClarabelOptionsBuilder::with_clarabel_setting(settings)
2008                    .allow_almost_solved(true)
2009                    .build()?;
2010                let (result, solution) = copp3_socp_expert(&copp3_problem, &options)?;
2011                if let Some(result) = result {
2012                    result
2013                } else {
2014                    crate::verbosity_log!(
2015                        crate::diag::Verbosity::Debug,
2016                        "{:?}",
2017                        CoppError::ClarabelSolverStatus(
2018                            "copp3_socp (case 4)".into(),
2019                            solution.status,
2020                        )
2021                    );
2022                    continue;
2023                }
2024            };
2025            let tc_copp3_case4 = start.elapsed().as_secs_f64() * 1E3;
2026            let (_, obj_case4) = {
2027                let copp3_problem = Copp3ProblemBuilder::new(
2028                    &mut robot,
2029                    &objectives_test,
2030                    0,
2031                    &a_ra0,
2032                    (0.0, 0.0),
2033                    (0.0, 0.0),
2034                )
2035                .build_with_linearization()?;
2036                objective_value_copp3_opt(&copp3_problem, &a_case4, &b_case4, num_stationary)
2037            };
2038            if obj_case4[1] < obj_case1[1] - 1E-3 || obj_case4[3] - 1E-3 > obj_case1[3] {
2039                crate::verbosity_log!(
2040                    crate::diag::Verbosity::Summary,
2041                    "omega = {omega:?}\nphi = {phi:?}"
2042                );
2043                crate::verbosity_log!(
2044                    crate::diag::Verbosity::Summary,
2045                    "Case 1: obj_time = {}, obj_thermal_energy = {}, obj_linear = {}",
2046                    obj_case1[0],
2047                    obj_case1[1],
2048                    obj_case1[3]
2049                );
2050                crate::verbosity_log!(
2051                    crate::diag::Verbosity::Summary,
2052                    "Case 4: obj_time = {}, obj_thermal_energy = {}, obj_linear = {}",
2053                    obj_case4[0],
2054                    obj_case4[1],
2055                    obj_case4[3]
2056                );
2057                crate::verbosity_log!(
2058                    crate::diag::Verbosity::Summary,
2059                    "Interesting... Cases 1 and 4"
2060                );
2061            }
2062            if obj_case4[2] < obj_case2[2] - 1E-3 || obj_case4[3] - 1E-3 > obj_case2[3] {
2063                crate::verbosity_log!(
2064                    crate::diag::Verbosity::Summary,
2065                    "omega = {omega:?}\nphi = {phi:?}"
2066                );
2067                crate::verbosity_log!(
2068                    crate::diag::Verbosity::Summary,
2069                    "Case 2: obj_time = {}, obj_total_variation_torque = {}, obj_linear = {}",
2070                    obj_case2[0],
2071                    obj_case2[2],
2072                    obj_case2[3]
2073                );
2074                crate::verbosity_log!(
2075                    crate::diag::Verbosity::Summary,
2076                    "Case 4: obj_time = {}, obj_total_variation_torque = {}, obj_linear = {}",
2077                    obj_case4[0],
2078                    obj_case4[2],
2079                    obj_case4[3]
2080                );
2081                crate::verbosity_log!(
2082                    crate::diag::Verbosity::Summary,
2083                    "Interesting... Cases 2 and 4"
2084                );
2085            }
2086
2087            succeed += 1;
2088
2089            if flag_print_step {
2090                crate::verbosity_log!(
2091                    crate::diag::Verbosity::Summary,
2092                    "Exp #{}:\n Case 0: tc={:.3}ms, obj={:?}\n Case 1: tc={:.3}ms, obj={:?}\n Case 2: tc={:.3}ms, obj={:?}\n Case 3: tc={:.3}ms, obj={:?}\n Case 4: tc={:.3}ms, obj={:?}",
2093                    i_exp + 1,
2094                    tc_copp3_case0,
2095                    obj_case0,
2096                    tc_copp3_case1,
2097                    obj_case1,
2098                    tc_copp3_case2,
2099                    obj_case2,
2100                    tc_copp3_case3,
2101                    obj_case3,
2102                    tc_copp3_case4,
2103                    obj_case4
2104                );
2105            }
2106
2107            tc_sum_case0 += tc_copp3_case0;
2108            tc_sum_case1 += tc_copp3_case1;
2109            tc_sum_case2 += tc_copp3_case2;
2110            tc_sum_case3 += tc_copp3_case3;
2111            tc_sum_case4 += tc_copp3_case4;
2112            for i in 0..obj_case0.len() {
2113                obj_sum_case0[i] += obj_case0[i];
2114                obj_sum_case1[i] += obj_case1[i];
2115                obj_sum_case2[i] += obj_case2[i];
2116                obj_sum_case3[i] += obj_case3[i];
2117                obj_sum_case4[i] += obj_case4[i];
2118            }
2119        }
2120
2121        for i in 0..4 {
2122            obj_sum_case0[i] /= n_exp as f64;
2123            obj_sum_case1[i] /= n_exp as f64;
2124            obj_sum_case2[i] /= n_exp as f64;
2125            obj_sum_case3[i] /= n_exp as f64;
2126            obj_sum_case4[i] /= n_exp as f64;
2127        }
2128
2129        crate::verbosity_log!(
2130            crate::diag::Verbosity::Summary,
2131            "Average {n_exp} experiments (fail {}):\n Case 0: tc={:.3}ms, obj={obj_sum_case0:?}\n Case 1: tc={:.3}ms, obj={obj_sum_case1:?}\n Case 2: tc={:.3}ms, obj={obj_sum_case2:?}\n Case 3: tc={:.3}ms, obj={obj_sum_case3:?}\n Case 4: tc={:.3}ms, obj={obj_sum_case4:?}",
2132            n_exp - succeed,
2133            tc_sum_case0 / succeed as f64,
2134            tc_sum_case1 / succeed as f64,
2135            tc_sum_case2 / succeed as f64,
2136            tc_sum_case3 / succeed as f64,
2137            tc_sum_case4 / succeed as f64
2138        );
2139
2140        Ok(())
2141    }
2142}